diff options
| author | Jay V <[email protected]> | 2025-08-29 17:30:48 -0400 |
|---|---|---|
| committer | Jay V <[email protected]> | 2025-08-29 19:20:18 -0400 |
| commit | 28c341ad321e106c690244681cc0bcd2c9926851 (patch) | |
| tree | 44cbd16f7b39af46de30468451605421aef6023f /cloud/app/src/routes/workspace/[id].tsx | |
| parent | a05e6774129eb45668ddebbfa7eb41ec4c25746b (diff) | |
| download | opencode-28c341ad321e106c690244681cc0bcd2c9926851.tar.gz opencode-28c341ad321e106c690244681cc0bcd2c9926851.zip | |
ignore: cloud usage history
Diffstat (limited to 'cloud/app/src/routes/workspace/[id].tsx')
| -rw-r--r-- | cloud/app/src/routes/workspace/[id].tsx | 415 |
1 files changed, 258 insertions, 157 deletions
diff --git a/cloud/app/src/routes/workspace/[id].tsx b/cloud/app/src/routes/workspace/[id].tsx index bfeb06884..6000228dc 100644 --- a/cloud/app/src/routes/workspace/[id].tsx +++ b/cloud/app/src/routes/workspace/[id].tsx @@ -49,7 +49,60 @@ const createPortalUrl = action(async (returnUrl: string) => { return withActor(() => Billing.generatePortalUrl({ returnUrl })) }, "portalUrl") -export default function () { +const dummyUsageData = [ + { + model: "claude-3-5-sonnet-20241022", + inputTokens: 1250, + outputTokens: 890, + reasoningTokens: 150, + cacheReadTokens: 0, + cacheWriteTokens: 45, + cost: 12340000, + timeCreated: new Date("2025-01-28T10:30:00Z"), + }, + { + model: "claude-3-haiku-20240307", + inputTokens: 2100, + outputTokens: 450, + reasoningTokens: null, + cacheReadTokens: 120, + cacheWriteTokens: 0, + cost: 5670000, + timeCreated: new Date("2025-01-27T15:22:00Z"), + }, + { + model: "claude-3-5-sonnet-20241022", + inputTokens: 850, + outputTokens: 1200, + reasoningTokens: 220, + cacheReadTokens: 30, + cacheWriteTokens: 15, + cost: 18990000, + timeCreated: new Date("2025-01-27T09:15:00Z"), + }, + { + model: "claude-3-opus-20240229", + inputTokens: 3200, + outputTokens: 1800, + reasoningTokens: 400, + cacheReadTokens: 0, + cacheWriteTokens: 100, + cost: 45670000, + timeCreated: new Date("2025-01-26T14:45:00Z"), + }, + { + model: "claude-3-haiku-20240307", + inputTokens: 650, + outputTokens: 280, + reasoningTokens: null, + cacheReadTokens: 200, + cacheWriteTokens: 0, + cost: 2340000, + timeCreated: new Date("2025-01-25T16:18:00Z"), + }, +] + +export default function() { const actor = createAsync(() => getActor()) onMount(() => { console.log("MOUNTED", actor()) @@ -69,6 +122,32 @@ export default function () { return date.toLocaleDateString() } + const formatDateForTable = (date: Date) => { + const options: Intl.DateTimeFormatOptions = { + day: "numeric", + month: "short", + hour: "numeric", + minute: "2-digit", + hour12: true, + } + return date.toLocaleDateString("en-GB", options).replace(",", ",") + } + + const formatDateUTC = (date: Date) => { + const options: Intl.DateTimeFormatOptions = { + weekday: "short", + year: "numeric", + month: "short", + day: "numeric", + hour: "numeric", + minute: "2-digit", + second: "2-digit", + timeZoneName: "short", + timeZone: "UTC", + } + return date.toLocaleDateString("en-US", options) + } + const formatKey = (key: string) => { if (key.length <= 11) return key return `${key.slice(0, 7)}...${key.slice(-4)}` @@ -155,173 +234,195 @@ export default function () { return ( <div data-slot="root"> - {/* Actor Section */} - <section data-slot="actor-section"> - <div data-slot="section-title"> - <h1>Actor</h1> - <p>Current authenticated user information and session details.</p> - </div> - <div>{JSON.stringify(actor())}</div> + {/* Title */} + <section data-slot="title-section"> + <h1>Gateway</h1> + <p> + Coding models optimized for use with opencode. <a href="/docs">Learn more</a>. + </p> </section> - {/* API Keys Section */} - <section data-slot="keys-section"> - <div data-slot="section-title"> - <h1>API Keys</h1> - <p>Manage your API keys for accessing opencode services.</p> - </div> - <Show - when={!showCreateForm()} - fallback={ - <div data-slot="create-form"> - <input - data-component="input" - type="text" - placeholder="Enter key name" - value={keyName()} - onInput={(e) => setKeyName(e.currentTarget.value)} - onKeyPress={(e) => e.key === "Enter" && handleCreateKey()} - /> - <div data-slot="form-actions"> - <button - color="primary" - disabled={createKeySubmission.pending || !keyName().trim()} - onClick={handleCreateKey} - > - {createKeySubmission.pending ? "Creating..." : "Create"} - </button> - <button - color="ghost" - onClick={() => { - setShowCreateForm(false) - setKeyName("") - }} - > - Cancel - </button> - </div> - </div> - } - > - <button - color="primary" - onClick={() => { - console.log("clicked") - setShowCreateForm(true) - }} - > - Create API Key - </button> - </Show> - <div data-slot="key-list"> - <For - each={keys()} + <div data-slot="sections"> + {/* Actor Section */} + <section data-slot="actor-section"> + <div data-slot="section-title"> + <h2>Actor</h2> + <p>Current authenticated user information and session details.</p> + </div> + <div>{JSON.stringify(actor())}</div> + </section> + + {/* API Keys Section */} + <section data-slot="keys-section"> + <div data-slot="section-title"> + <h2>API Keys</h2> + <p>Manage your API keys for accessing opencode services.</p> + </div> + <Show + when={!showCreateForm()} fallback={ - <div data-slot="empty-state"> - <p>Create an API key to access opencode gateway</p> + <div data-slot="create-form"> + <input + data-component="input" + type="text" + placeholder="Enter key name" + value={keyName()} + onInput={(e) => setKeyName(e.currentTarget.value)} + onKeyPress={(e) => e.key === "Enter" && handleCreateKey()} + /> + <div data-slot="form-actions"> + <button + color="primary" + disabled={createKeySubmission.pending || !keyName().trim()} + onClick={handleCreateKey} + > + {createKeySubmission.pending ? "Creating..." : "Create"} + </button> + <button + color="ghost" + onClick={() => { + setShowCreateForm(false) + setKeyName("") + }} + > + Cancel + </button> + </div> </div> } > - {(key) => ( - <div data-slot="key-item"> - <div data-slot="key-info"> - <div data-slot="key-name">{key.name}</div> - <div data-slot="key-value">{formatKey(key.key)}</div> - <div data-slot="key-meta"> - Created: {formatDate(key.timeCreated)} - {key.timeUsed && ` • Last used: ${formatDate(key.timeUsed)}`} - </div> + <button + color="primary" + onClick={() => { + console.log("clicked") + setShowCreateForm(true) + }} + > + Create API Key + </button> + </Show> + <div data-slot="key-list"> + <For + each={keys()} + fallback={ + <div data-slot="empty-state"> + <p>Create an API key to access opencode gateway</p> </div> - <div data-slot="key-actions"> - <button color="ghost" onClick={() => copyToClipboard(key.key)} title="Copy API key"> - Copy - </button> - <button color="ghost" onClick={() => handleDeleteKey(key.id)} title="Delete API key"> - Delete - </button> + } + > + {(key) => ( + <div data-slot="key-item"> + <div data-slot="key-info"> + <div data-slot="key-name">{key.name}</div> + <div data-slot="key-value">{formatKey(key.key)}</div> + <div data-slot="key-meta"> + Created: {formatDate(key.timeCreated)} + {key.timeUsed && ` • Last used: ${formatDate(key.timeUsed)}`} + </div> + </div> + <div data-slot="key-actions"> + <button color="ghost" onClick={() => copyToClipboard(key.key)} title="Copy API key"> + Copy + </button> + <button color="ghost" onClick={() => handleDeleteKey(key.id)} title="Delete API key"> + Delete + </button> + </div> </div> - </div> - )} - </For> - </div> - </section> + )} + </For> + </div> + </section> - {/* Balance Section */} - <section data-slot="balance-section"> - <div data-slot="section-title"> - <h1>Balance</h1> - <p>Manage your billing and add credits to your account.</p> - </div> - <div data-slot="balance"> - <p> - {(() => { - const balanceStr = ((billingInfo()?.billing?.balance ?? 0) / 100000000).toFixed(2) - return `$${balanceStr === "-0.00" ? "0.00" : balanceStr}` - })()} - </p> - <button color="primary" disabled={isLoading()} onClick={handleBuyCredits}> - {isLoading() ? "Loading..." : "Buy Credits"} - </button> - </div> - </section> + {/* Balance Section */} + <section data-slot="balance-section"> + <div data-slot="section-title"> + <h2>Balance</h2> + <p>Manage your billing and add credits to your account.</p> + </div> + <div data-slot="balance"> + <p> + {(() => { + const balanceStr = ((billingInfo()?.billing?.balance ?? 0) / 100000000).toFixed(2) + return `$${balanceStr === "-0.00" ? "0.00" : balanceStr}` + })()} + </p> + <button color="primary" disabled={isLoading()} onClick={handleBuyCredits}> + {isLoading() ? "Loading..." : "Buy Credits"} + </button> + </div> + </section> - {/* Payments Section */} - <section data-slot="payments-section"> - <div data-slot="section-title"> - <h1>Payments History</h1> - <p>Your recent payment transactions.</p> - </div> - <div data-slot="payments-list"> - <For - each={billingInfo()?.payments} - fallback={ - <div data-slot="empty-state"> - <p>No payment history yet. Your payments will appear here after your first purchase.</p> - </div> - } - > - {(payment) => ( - <div data-slot="payment-item"> - <span data-slot="payment-id">{payment.id}</span> - {" | "} - <span data-slot="payment-amount">${((payment.amount ?? 0) / 100000000).toFixed(2)}</span> - {" | "} - <span data-slot="payment-date">{new Date(payment.timeCreated).toLocaleDateString()}</span> - </div> - )} - </For> - </div> - </section> + {/* Payments Section */} + <Show when={billingInfo() && billingInfo()!.payments.length > 0}> + <section data-slot="payments-section"> + <div data-slot="section-title"> + <h2>Payments History</h2> + <p>Your recent payment transactions.</p> + </div> + <div data-slot="payments-list"> + <For each={billingInfo()?.payments}> + {(payment) => ( + <div data-slot="payment-item"> + <span data-slot="payment-id">{payment.id}</span> + {" | "} + <span data-slot="payment-amount">${((payment.amount ?? 0) / 100000000).toFixed(2)}</span> + {" | "} + <span data-slot="payment-date">{new Date(payment.timeCreated).toLocaleDateString()}</span> + </div> + )} + </For> + </div> + </section> + </Show> - {/* Usage Section */} - <section data-slot="usage-section"> - <div data-slot="section-title"> - <h1>Usage History</h1> - <p>Your recent API usage and costs.</p> - </div> - <div data-slot="usage-list"> - <For - each={billingInfo()?.usage} - fallback={ - <div data-slot="empty-state"> - <p>No API usage yet. Your usage history will appear here after your first API calls.</p> - </div> - } - > - {(usage) => ( - <div data-slot="usage-item"> - <span data-slot="usage-model">{usage.model}</span> - {" | "} - <span data-slot="usage-tokens">{usage.inputTokens + usage.outputTokens} tokens</span> - {" | "} - <span data-slot="usage-cost">${((usage.cost ?? 0) / 100000000).toFixed(4)}</span> - {" | "} - <span data-slot="usage-date">{new Date(usage.timeCreated).toLocaleDateString()}</span> - </div> - )} - </For> - </div> - </section> + {/* Usage Section */} + <section data-slot="usage-section"> + <div data-slot="section-title"> + <h2>Usage History</h2> + <p>Your recent API usage and costs.</p> + </div> + <div data-slot="usage-table"> + <Show + when={dummyUsageData.length > 0} + fallback={ + <div data-slot="empty-state"> + <p>Make your first API call to get started.</p> + </div> + } + > + <table data-slot="usage-table-element"> + <thead> + <tr> + <th>Date</th> + <th>Model</th> + <th>Tokens</th> + <th>Cost</th> + </tr> + </thead> + <tbody> + <For each={dummyUsageData}> + {(usage) => { + const totalTokens = usage.inputTokens + usage.outputTokens + (usage.reasoningTokens || 0) + const date = new Date(usage.timeCreated) + return ( + <tr> + <td data-slot="usage-date" title={formatDateUTC(date)}> + {formatDateForTable(date)} + </td> + <td data-slot="usage-model">{usage.model}</td> + <td data-slot="usage-tokens">{totalTokens.toLocaleString()}</td> + <td data-slot="usage-cost">${((usage.cost ?? 0) / 100000000).toFixed(4)}</td> + </tr> + ) + }} + </For> + </tbody> + </table> + </Show> + </div> + </section> + </div> </div> ) } |
